#include "Game.h"

using std::vector;

Game::Game ()
	:_count(0),
	_score(0),
	_level(2),
	_top_level(3),
	_lives(100),
	_monster_kill_score(100)
{
	LevelLoader level_data(_level);
	loadMap(level_data);
	loadObjects(level_data);
	plotMovablesToMap();
}

int Game::score()
{
	return _score;
}

int Game::digdugLives()
{
	return _lives;
}

int Game::level()
{
	return _level;
}

void Game::loadMap(LevelLoader &level_data)
{
	_map.set(level_data.getMapData());
}

Map Game::map()
{
	return _map;
}

GameResult Game::update(Action instruction)
{
	if(_count%5 ==0)
	{
		//clock_t begin=clock();
		//routeMonsters(_map,  _digdug.location(), _monsters);
		//clock_t end=clock();
		//cout << "Time elapsed: " << double(diffclock(end,begin)) << " ms"<< endl;
	}

	manageDigdugActions(instruction);

	updateMonsters();
	
	PlayResult move_result = moveObjects();
	GameResult result = manageScore(move_result);
	
	_count++;
	return result;
}

GameResult Game::manageScore(PlayResult move_result)
{
	if(move_result == DIGDUG_DIES)
	{
		if(_lives != 1)
		{
			_lives--;
			_map.removeAllMovables();
			LevelLoader level_data(_level);
			loadObjects(level_data);
		}
			
		else
			return GAME_OVER;
	}

	else if(move_result == MONSTER_DIES)
	{
        _score = _score + _monster_kill_score;

		if(_monsters.size() == 0)
		{
           	if(_level != _top_level)
			{
              	_level++;
				LevelLoader level_data(_level);
				loadMap(level_data);
				loadObjects(level_data);
				plotMovablesToMap();
			}
			else
       				return WIN;
		}

	}

	return IN_PROGRESS;
}

void Game::loadObjects(LevelLoader &level_data)
{
	_digdug = Digdug(level_data.getDigdugPosition());

	_monsters.clear();

	vector<Point> monster_locations =  level_data.getMonstersPosition();
	for(int i = 0; i <monster_locations.size(); i++)
	{
		_monsters.push_back(Monster(monster_locations.at(i)));
	}

	_projectile = Projectile(level_data.getDigdugPosition());
}

void Game::manageDigdugActions(Action instruction)
{
	//if the instruction to shoot is given for the first time, check whether shooting is valid and place the projectile in the correct position if it is.
	if(instruction == SHOOT && _projectile.isShooting() == false)
	{
		_projectile.allowMove(_digdug.direction());
		_projectile.location(_digdug.location());
	}

	// Shoot button must be held down in order to keep shooting
	else if(instruction != SHOOT && _projectile.isShooting() == true)
	{
		_projectile.stopShot();
		_map.removeMovable(_projectile.location(), _projectile.radius());
		for (int i = 0; i != _monsters.size(); i++)
		{
			_monsters.at(i).allowMove(_monsters.at(i).direction());
		}

		/*The order of plotting ensures that projectile is plotted, then digdug then monsters.
		If the projectile shares the same location as digdug, it will be overwritten on the map when digdug is replotted on top of it*/
		_projectile.location(_digdug.location());
	}

	if(instruction != SHOOT && instruction != NO_ACTION)
	{
		Direction dir = static_cast<Direction>(instruction);
		_digdug.allowMove(dir);
	}
}

void Game::updateMonsters()
{
	for (int i = 0; i != _monsters.size(); i++)
	{
		_monsters.at(i).updateMoveDirection(_digdug.location(), _map);
		
		bool initial_ghost_status = _monsters.at(i).isGhost();
		
		//if((_count+i* + 5)%30 == 0)
		//	_monsters.at(i).updateGhostMode(_digdug.location());
		if (initial_ghost_status !=  _monsters.at(i).isGhost())	//check if monster has turned into ghost
			_map.removeMovable(_monsters.at(i).location(), _monsters.at(i).radius());
	}
}


PlayResult Game::moveObjects()
{
	//Move the projectile first
	if(_projectile.isShooting())
	{
		_map.removeMovable(_projectile.location(),_projectile.radius());

		DetectionResult projectile_result = _projectile.move(_map);
		if(projectile_result == CAN_MOVE)
		{
			_map.plotMovable(_projectile.type(), _projectile.location(), _projectile.radius());
		}
		else if(projectile_result == MONSTER_DEAD)
		{
         bool monster_killed = destroyClosestMonster();
			
			if(monster_killed == true)
			{
				//remove projectile from map
				_map.removeMovable(_projectile.location(), _projectile.radius());
				//place projectile in the same position as digdug
				_projectile.location(_digdug.location());
				cout<<"MONSTER KILLED";
				return MONSTER_DIES;
			}
		}
	}


	//Move digdug
	_map.removeMovable(_digdug.location(),_digdug.radius());
	DetectionResult digdug_result = _digdug.move(_map);
	if(digdug_result == DIGDUG_DEAD)
	{return DIGDUG_DIES;}
	_map.plotMovable(_digdug.type(), _digdug.location(),_digdug.radius());
	if(_projectile.isShooting() == false)	//Update projectiles position to match digdug's if the projectile is not being fired
	{_projectile.location(_digdug.location());}
	
	//Move Monsters
	for(int i = 0; i != _monsters.size(); i++)
	{
		if((_count +1)%4 ==0)
		{
			if(_monsters.at(i).isGhost() == false)	//Only remove the monster to the map if it is not in ghost mode.
			{
				_map.removeMovable(_monsters.at(i).location(), _monsters.at(i).radius());
			}
			DetectionResult monster_result = _monsters.at(i).move(_map);

			if(monster_result == DIGDUG_DEAD)
			{
				return DIGDUG_DIES;
			}
			if(_monsters.at(i).isGhost() == false)	//Only replot the monster to the map if it is not in ghost mode.
			{
				_map.plotMovable(_monsters.at(i).type(), _monsters.at(i).location(), _monsters.at(i).radius());
			}
		}

	}
	return NO_CHANGE;
}


vector<Movables*> Game::movable_ptrs()
{
	//No memory is being allocated, therefore smart pointers are not being used!
	vector<Movables*> movables;

	// First add projectile
	Movables *projectile_ptr = &_projectile;
	movables.push_back(projectile_ptr);

	//Second add digdug
	Movables *digdug_ptr = &_digdug;
	movables.push_back(digdug_ptr);

	//Last add all the monsters
	for (int i = 0; i != _monsters.size(); i++)
	{
		movables.push_back(&_monsters.at(i));
	}
	return movables;
}

void Game::plotMovablesToMap()
{
	vector<Movables*> ptrs = movable_ptrs();
	for (int i = 0; i != ptrs.size(); i++)
	{
		_map.plotMovable((ptrs.at(i))->type(), (ptrs.at(i))->location(), (ptrs.at(i))->radius());
	}
}

bool Game::destroyClosestMonster()
{
	int delta_x = delta(_projectile.direction()).at(0);
	int delta_y = delta(_projectile.direction()).at(1);

	int monster_number = 0;
	bool monster_found = false;

	for(int element = -RADIUS_OF_MOVABLE - 1; element <= RADIUS_OF_MOVABLE +1; element++)
	{

		int monster_x = _projectile.location().x() + delta_x*(2*RADIUS_OF_MOVABLE +1) + delta_y*element;
		int monster_y = _projectile.location().y() + delta_y*(2*RADIUS_OF_MOVABLE +1) + delta_x*element;
		Point monster_location = Point(monster_x, monster_y);
	
		for(int i = 0; i!= _monsters.size(); i++)
		{
           if(_monsters.at(i).location() == monster_location)
			{
				monster_number = i;
				monster_found = true;
			}				
		}
	}
		
	// In the case that the monster moves into the projectile before the projectile moves into the monster and the closest monster's exact location cannot be found
	//using the previous method
	if (monster_found = false) 
	{
		int dx = 0;
		int dy = 0;

		int current_total_distance = 0;
		int prev_total_distance = 100;
		int monster_number = 0;


		for(int i = 0; i!= _monsters.size(); i++)
		{
			dx = _monsters.at(i).location().x() - _projectile.location().x();
			dy = _monsters.at(i).location().y() - _projectile.location().y();
			current_total_distance = abs(dx) - abs(dy);

			if(abs(current_total_distance) < abs(prev_total_distance))
			{
				prev_total_distance = current_total_distance;
				monster_number = i;
			}				
	
		}
	}
	
	if(_monsters.at(monster_number).blowUp() == true)
	{
		//remove dead monster from map and delete him
  		_map.removeMovable(_monsters.at(monster_number).location(), _monsters.at(monster_number).radius());
		vector<Monster>::iterator iter = _monsters.begin();
		_monsters.erase(iter + monster_number);
		//remove the projectile from the map and delete it
		_map.removeMovable(_projectile.location(), _projectile.radius());
		_projectile.stopShot();
		return true;
	}

	return false;
}

